// ==============================================================================
// AsyncMessageReceiverGateway provides functionality for receiving messages 
// asynchronously from MSMQ.
// 
// AsyncMessageReceiverGateway.cs
// @author Abel Perez <java.aperez@gmail.com>
// ==============================================================================

#region Imports

using System;
using System.Messaging;
using System.Xml;

#endregion

namespace Justa.Messaging
{
    /// <summary>
    /// AsyncMessageReceiverGateway provides the ability to receive messages 
    /// asynchronously from MSMQ.  This gateway essentially waits around for 
    /// incomming messages from MSMQ and fires an event to a multicast delegate 
    /// for each incomming message.  The default message formatter used by 
    /// this gateway is an XML message formatter, the most common use for this 
    /// gateway is to recieve xml based messages.  In order to use this gateway 
    /// you must first set an event handler and invoke the Start operation.
    /// 
    /// For Example, the following code snippet constructs an async gateway 
    /// receiver with an event handler that will be invoked asynchronously 
    /// everytime a new message is received:
    /// 
    /// <code>
    ///     string destination = "Unified.AutoLoan.CarsDirect.Ping";
    ///     string name = "CarsDirectGateway";
    ///     AsyncMessageReceiverGateway gateway = new AsyncMessageReceiverGateway(destination, name);
    ///     
    ///     AsyncMessageReceiverGateway.OnMessageEvent handler = 
    ///             new AsyncMessageReceiverGateway.OnMessageEvent(ProcessMessage);
    ///
    ///     gateway.AddEventHandler(handler);
    ///     gateway.Start();
    /// </code>
    /// 
    /// Important note: This gateway will not prevent the current thread from going out of 
    /// scope, therefore in order to ensure this gateway will continue to receive messages, 
    /// the following is required:
    /// 
    /// <para>
    /// 1. This gateway runs in a stand-alone application that prevents 
    ///    the main thread of execution from going out of scope.
    /// 2. This gateway runs in an environment like a web container that manages 
    ///    the lifetime of the application the gateway is running under.
    /// </para>
    /// </summary>
    /// 
    public class AsyncMessageReceiverGateway : GenericMessageGateway
    {

        /// <summary>
        /// The default message queue this gateway will receive messages from.
        /// </summary>
        private MessageQueue messageQueue;

        /// <summary>
        /// Multicast delegate that this gateway will invoke for each message received.
        /// </summary>
        /// 
        public delegate void OnMessageEvent(Message message);

        /// <summary>
        /// Multicast delegate instance.
        /// </summary>
        /// 
        private OnMessageEvent receiver;


        /// <summary>
        /// Constructs this gateway with the given destination as the default message
        /// queue this gateway will receive messages from.  
        /// </summary>
        /// 
        /// <param name="destination"></param>
        public AsyncMessageReceiverGateway(string destination)
            : this(destination, null)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="destination"></param>
        /// <param name="name"></param>
        public AsyncMessageReceiverGateway(string destination, string name)
        {
            if (name == null)
            {
                this.Name = DEFAULT_NAME;
            }
            else
            {
                this.Name = name;
            }

            this.messageQueue = GetMessageQueue(destination);
            if (messageQueue == null)
            {
                throw new ApplicationException("Failed to initialize MessageReceiverGateway.");
            }

            this.messageQueue.Formatter = new System.Messaging.XmlMessageFormatter(new String[] { "System.String" });
            receiver = new OnMessageEvent(DefaultEvent);
        }

        /// <summary>
        /// Starts the asynchronous recieve process for this gateway.
        /// </summary>
        /// 
        public void Start()
        {
            try
            {
                this.messageQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnReceiveCompleted);
                this.messageQueue.BeginReceive();
            }
            catch (MessageQueueException exception)
            {
                throw new ApplicationException("Failed to initialize MessageReceiverGateway.", exception);
            }
        }

        /// <summary>
        /// Adds an event handling delegate to the multicast delegate that 
        /// this gateway will fire per each message recieved.
        /// </summary>
        /// 
        /// <param name="handler">Event handler delegate that will recieve events about incomming messages.</param>
        /// 
        public void AddEventHandler(OnMessageEvent handler)
        {
            receiver += handler;
        }

        /// <summary>
        /// Default operation that will be invoked for each incomming message.
        /// </summary>
        /// 
        /// <param name="message">The incomming message received by this gateway.</param>
        /// 
        private void DefaultEvent(Message message)
        {
        }

        /// <summary>
        /// This operation is invoked for each incomming message received by this gateway.  
        /// Each message received is converted to an xml string and passed along to the 
        /// multicast delegate this gateway is configured to use.  This operation does not 
        /// process any messages beyond the xml convertaion.  Domain specific processing of 
        /// any messages is handled by the registered delegates.
        /// </summary>
        /// 
        /// <param name="source">The source who fired the event.</param>
        /// <param name="asyncResult">The receive's asynchronous result.</param>
        /// 
        private void OnReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
        {
            MessageQueue destination = null;
            
            try
            {
                destination = (MessageQueue)source;
                Message message = destination.EndReceive(asyncResult.AsyncResult);
                receiver(message);
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
            }
            finally
            {
                // resume async receive
                destination.BeginReceive();
            }
        }
    }
}